7W - 실리움 성능 - 쿠버네티스 기본
개요
이번 주차는 운영에서 중요한 업무 중 하나인 성능 최적화에 대한 부분을 알아본다.
실리움의 성능 튜닝을 알아보기 이전에, 쿠버네티스에서 신경 써야 할 성능 포인트를 먼저 짚고 들어간다.
아울러 성능 측정에 도움을 주는 툴도 알아보도록 한다.
kube-burner
kube-burner는 쿠버네티스 클러스터의 성능을 테스트하는 도구로 불내는데(?) 유용하다.[1]
쿠버네티스 Go 클라이언트 라이브러리인 client-go를 이용에 테스트에 용이하도록 기능을 만든 어플리케이션이다.
기본적으로 api 서버에 대한 여러 요청을 날릴 수 있고 프로메테우스 기반 모니터링, 측정, 알람 기능도 갖추고 있다.
테스트에 사용할 양식 파일을 먼저 만들고, 이후에 kube-burner에서 지원하는 커스텀 리소스 양식으로 이를 참조한 뒤에 사용한다.
그래서 원하는 테스트 상황을 마음대로 만들 수 있다는 장점이 있다.
사용법
사용할 커맨드가 많지 않다.
check-alerts Evaluate alerts for the given time range 주어진 시간 범위 내의 알람 평가
completion Generates completion scripts for bash shell
destroy Destroy old namespaces labeled with the given UUID. 사용된 네임스페이스 삭제
health-check Check for Health Status of the cluster 클러스터 헬스체크
help Help about any command
import Import metrics tarball tarball 압축 파일에서 메트릭 불러오기
index Index kube-burner metrics 메트릭 인덱싱(start, end 시간으로 범위 지정)
init Launch benchmark 벤치마크 실행
measure Take measurements for a given set of resources without running workload 워크로드 실행 없이 리소스만 테스트
version Print the version number of kube-burner
보다시피 init이 벤치마킹을 수행하는 명령어이다.
여기에 -c
옵션으로 설정 파일을 넣어서 실행한다.
벤치마킹의 반환 코드는 다음의 뜻을 가진다.
- 0 - 정상 종료
- 1 - 회복 불가능 에러(api 인가, 파싱 실패 등)
- 2 - 타임아웃.
--timeout
인자를 받았을 때 나옴 - 3 - 심각 레벨 알람이 발생할 때 나옴
- 4 - 임계값과 같은 측정치에서 조건을 넘겼을 때
설정 파일 작성법
go-template 양식의 파일을 지원한다.
metricsEndpoints:
{{ if .OS_INDEXING }}
- prometheusURL: http://localhost:9090
indexer:
type: opensearch
esServers: ["{{ .ES_SERVER }}"]
defaultIndex: {{ .ES_INDEX }}
{{ end }}
{{ if .LOCAL_INDEXING }}
- prometheusURL: http://localhost:9090
indexer:
type: local
metricsDirectory: {{ .METRICS_FOLDER }}
{{ end }}
이때 파일은 크게 몇 가지 대범주의 필드로 작성한다.
- global
- measurements - 측정할 값들에 대한 리스트.
- gc - 생성된 네임스페이스 삭제 여부
- timeout - 벤치마킹 타임아웃
- requestTimeout - 단일 api 요청 타임아웃
- job - 벤치마킹 작업 단위
- name
- jobType - create, delete, read, patch
- jobIterations
- waitWhenFinished - 한 잡이 끝나고 순회돌지 여부
- qps - 초당 오브젝트 요청 수(한 잡에서 api 서버로 쿼리 날리는 제한)
- burst - 쓰로틀을 위한 동시 요청 수
- objects - 생성할 오브젝트 리스트
- objectTemplate - 오브젝트 파일 경로
- read 유형의 잡일 때는 이거 대신 그냥 kind와 라벨 셀렉터만 써도 된다.
- patch 유형의 잡일 때는 patchType으로 어떻게 패치할지 정하고, objectTemplate으로 패치 파일을 둔다.
- delete 유형의 잡일 때는 kind를 써도 된다.
- replicas
- inputVars - 오브젝트에 넣을 임의 변수
- wait
- waitOptions - 어떤 오브젝트가 ready가 될 때까지 기다린 후 실행되며, 순서를 지정할 때 유용하다.
- runOnce - 잡 순회 간 한번만 실행할 리소스라면 설정
- objectTemplate - 오브젝트 파일 경로
- watchers - 감시할 리소스 리스트
- kind, apiVersion, labelSelector로 감시하고자 하는 대상을 지정하는 잡을 만든다.
- replicas로 와쳐 리소스의 개수를 조절한다.
- preLoadImages - 잡을 위해 모든 노드에 미리 사용될 이미지를 받는 데몬셋 배치 여부
- executionMode - 잡 실행모드
- parallel, sequential
위 4가지 유형의 잡타입 말고, churn이란 동작을 하는 것도 가능하다.
jobs:
- name: cluster-density
jobIterations: 100
namespacedIterations: true
namespace: churning
churn: true
churnPercent: 20
churnDuration: 2h
churnDelay: 0s
objects:
- objectTemplate: deployment.yml
replicas: 10
- objectTemplate: service.yml
replicas: 10
churn은 이전 오브젝트를 지우고 재생성하는 동작이다.
잡 순회를 하며 이전 오브젝트를 일부 지우고 그대로 새로 리소스를 만드는 식으로 동작한다.
휘젓는 느낌이라 churn으로 부르는 모양이다.
오브젝트 파일
기본으로 오브젝트에 주입되는 변수는 다음과 같다.
- Iteration
- Replica
- Jobname
- UUID
- RunID
설명은 생략하고 사용예시만 본다.
apiVersion: v1
kind: Service
metadata:
name: sleep-app-{{.Iteration}}-{{.Replica}}
labels:
name: my-app-{{.Iteration}}-{{.Replica}}
spec:
selector:
app: sleep-app-{{.Iteration}}-{{.Replica}}
ports:
- name: serviceport
protocol: TCP
port: "{{.port}}"
targetPort: "{{.targetPort}}"
type: ClusterIP
여기에 여러 함수를 사용할 수 있는데, 기본 고랭 템플릿과 sprig 라이브러리[2]의 함수들이 지원된다.
측정 지표
위의 measurements에서 설정할 수 있는 지표는 여러 가지가 있다.
기본적으로 지표를 넣으면 측정 수치가 나온다(아래는 podLatency 예시).
{
"timestamp": "2020-11-15T20:28:59.598727718Z",
"schedulingLatency": 4,
"initializedLatency": 20,
"containersReadyLatency": 2997,
"podReadyLatency": 2997,
"metricName": "podLatencyMeasurement",
"uuid": "c40b4346-7af7-4c63-9ab4-aae7ccdd0616",
"namespace": "kubelet-density",
"podName": "kubelet-density-13",
"nodeName": "worker-001",
"jobName": "create-pods",
"jobIteration": "2",
"replica": "3",
}
이에 대한 메트릭 지표는 다음과 같은 방식으로 합쳐진다.
{
"quantileName": "Ready",
"uuid": "23c0b5fd-c17e-4326-a389-b3aebc774c82",
"P99": 3774,
"P95": 3510,
"P50": 2897,
"max": 3774,
"avg": 2876.3,
"timestamp": "2020-11-15T22:26:51.553221077+01:00",
"metricName": "podLatencyQuantilesMeasurement",
},
{
"quantileName": "PodScheduled",
"uuid": "23c0b5fd-c17e-4326-a389-b3aebc774c82",
"P99": 64,
"P95": 8,
"P50": 5,
"max": 64,
"avg": 5.38,
"timestamp": "2020-11-15T22:26:51.553225151+01:00",
"metricName": "podLatencyQuantilesMeasurement",
}
자세한 측정 지표들에 대한 정보는 문서를 확인한다.[3]
- podLatency
- jobLatency
- pvcLatency
- serviceLatency - 서비스의 엔드포인트들이 ready가 되면 시간을 재고, 이후 tcp 연결 수행을 하며 수치를 측정한다.
- netpolLatency - 두 개의 잡이 있을 때만 측정되는데, 예시를 참고하자.[4]
실습 환경 구성
성능 테스트를 하는 만큼, 자원이 불필요하게 많이 쓰이는 가상환경에서 실습하기에는 조금 부담스럽다.
그래서 이번에는 kind를 사용한다.
kind create cluster --name myk8s --image kindest/node:v1.33.2 --config - <<EOF
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30000
hostPort: 30000
- containerPort: 30001
hostPort: 30001
- containerPort: 30002
hostPort: 30002
- containerPort: 30003
hostPort: 30003
kubeadmConfigPatches: # Prometheus Target connection refused bind-address 설정
- |
kind: ClusterConfiguration
controllerManager:
extraArgs:
bind-address: 0.0.0.0
etcd:
local:
extraArgs:
listen-metrics-urls: http://0.0.0.0:2381
scheduler:
extraArgs:
bind-address: 0.0.0.0
- |
kind: KubeProxyConfiguration
metricsBindAddress: 0.0.0.0
EOF
각 컴포넌트들에 대해 모든 주소로 리스닝할 수 있도록 추가 인자를 전달한다.
이 설정을 넣어줘야 프로메테우스에서 메트릭을 편하게 수집할 수 있다.
이 다음에는 모니터링에 사용할 kube-ops-view, 프로메테우스를 세팅한다.
# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set service.main.type=NodePort,service.main.ports.http.nodePort=30003 --set env.TZ="Asia/Seoul" --namespace kube-system
open "http://localhost:30003/#scale=1.5"
open "http://localhost:30003/#scale=2"
# metrics-server
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm upgrade --install metrics-server metrics-server/metrics-server --set 'args[0]=--kubelet-insecure-tls' -n kube-system
# 확인
kubectl top node
kubectl top pod -A --sort-by='cpu'
kubectl top pod -A --sort-by='memory'
# 파라미터 파일 생성
cat <<EOT > monitor-values.yaml
prometheus:
prometheusSpec:
scrapeInterval: "15s"
evaluationInterval: "15s"
service:
type: NodePort
nodePort: 30001
grafana:
defaultDashboardsTimezone: Asia/Seoul
adminPassword: prom-operator
service:
type: NodePort
nodePort: 30002
alertmanager:
enabled: false
defaultRules:
create: false
prometheus-windows-exporter:
prometheus:
monitor:
enabled: false
EOT
cat monitor-values.yaml
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# 배포
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 75.15.1 \
-f monitor-values.yaml --create-namespace --namespace monitoring
# 웹 접속 실행
open http://127.0.0.1:30001 # macOS prometheus 웹 접속
open http://127.0.0.1:30002 # macOS grafana 웹 접속 ( admin , prom-operator )
그라파나 대시보드 두 개를 다운 받아 사용할 것이다.
curl -o 15661_rev2.json https://grafana.com/api/dashboards/15661/revisions/2/download
sed -i "s/\${DS__VICTORIAMETRICS-PROD-ALL}/prometheus/g" 15661_rev2.json
k create cm 15661 -n monitoring --from-file 15661_rev2.json
k -n monitoring label cm 15661 grafana_dashboard=1
curl -o 12006_rev1.json https://grafana.com/api/dashboards/12006/revisions/1/download
k create cm 12006 -n monitoring --from-file 12006_rev1.json
k -n monitoring label cm 12006 grafana_dashboard=1
컨피그맵으로 설정 파일을 넣고 grafana_dashboard=1
이라고 라벨만 달아주면 알아서 그라파나가 이를 인식하고 대시보드로 올려준다.
이번에 파드, 노드를 많이 넣어 테스트할 거라 유용한 대시보드들이다.
두번째 대시 보드는 조금 오래 됐지만 latency를 확인하기에 유용하다.
kube-burner 테스트
본격적으로 부하 테스트를 한번 시도해본다.
#
git clone https://github.com/kube-burner/kube-burner.git
cd kube-burner
# 바이너리 설치(추천) : mac M1
curl -LO https://github.com/kube-burner/kube-burner/releases/download/v1.17.3/kube-burner-V1.17.3-darwin-arm64.tar.gz # mac M
tar -xvf kube-burner-V1.17.3-darwin-arm64.tar.gz
curl -LO https://github.com/kube-burner/kube-burner/releases/download/v1.17.3/kube-burner-V1.17.3-linux-x86_64.tar.gz # Windows
tar -xvf kube-burner-V1.17.3-linux-x86_64.tar.gz
sudo cp kube-burner /usr/local/bin
먼저 사용법을 익히는 차원에서 디플로이먼트 1개를 생성하고 삭제하는 테스트를 해본다.
테스트용 설정 파일을 먼저 만든다.
global:
measurements:
- name: none
jobs:
- name: create-deployments
jobType: create
jobIterations: 1 # How many times to execute the job , 해당 job을 5번 반복 실행
qps: 1 # Limit object creation queries per second , 초당 최대 요청 수 (평균 속도 제한) - qps: 10이면 초당 10개 요청
burst: 1 # Maximum burst for throttle , 순간적으로 처리 가능한 요청 최대치 (버퍼) - burst: 20이면 한순간에 최대 20개까지 처리 가능
namespace: kube-burner-test
namespaceLabels: {kube-burner-job: delete-me}
waitWhenFinished: true # false
verifyObjects: false
preLoadImages: true # false
preLoadPeriod: 30s # default 1m
objects:
- objectTemplate: s1-deployment.yaml # 이걸 대상으로 삼겠다는 뜻
replicas: 1
오브젝트에는 테스트하고자 하는 양식 파일을 삽입하면 된다.
대충 설명하자면 생성하는 잡 수행 횟수는 한번, 1초에 쿼리는 1번에 최대로도 1번만 날릴 수 있다.
이때 테스트용 네임스페이스를 만들거고, 테스트에 사용될 이미지를 미리 받은 상태로 수행할 것이다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-{{ .Iteration}}-{{.Replica}}
labels:
app: test-{{ .Iteration }}-{{.Replica}}
kube-burner-job: delete-me
spec:
replicas: 1
selector:
matchLabels:
app: test-{{ .Iteration}}-{{.Replica}}
template:
metadata:
labels:
app: test-{{ .Iteration}}-{{.Replica}}
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
Go 템플릿이 적용되기 때문에 몇 가지 활용한 것이 보인다.
모니터링은 터미널과 kube-ops-view를 같이 쓴다.
네임스페이스도 늘어나고 노드도 늘어나니까.
# 모니터링 : 터미널, kube-ops-view
watch -d kubectl get ns,pod -A
kube-burner init -c s1-config.yaml --log-level debug
이미지를 미리 받아두는 데몬셋이 테스트 전 배치된 것을 확인할 수 있다.
전체 작업이 끝나면 해당 테스트 UUID를 이름으로 하는 로그 파일이 만들어진다.
별 건 아니고 출력됐던 로그가 그대로 들어있다.
간단하게 생성 잡에 대해 테스트를 했으니 삭제도 진행한다.
jobs:
- name: delete-deployments-namespace
qps: 500
burst: 500
namespace: kube-burner-test
jobType: delete
waitWhenFinished: true
objects:
- kind: Deployment
labelSelector: {kube-burner-job: delete-me}
apiVersion: apps/v1
- kind: Namespace
labelSelector: {kube-burner-job: delete-me}
라벨 기반으로 오브젝트를 선택하여 지우는 것을 보여주기 위해 이전 양식에서 라벨을 부착했다.
익숙해지는 차원에서 여러 값들을 수정하면서 테스트해본다.
qps는 초당 날리는 쿼리의 개수를 조절하는데, 이 기준은 잡 당 적용된다.
그래서 burst는 낮으면서 qps가 높아도 한번에 모든 잡이 실행되는 게 아니라 잡을 넘어가는데 시간이 발생한다.
반면 burst는 잡보다 더 큰 관점에서의 세팅값이다.
위처럼 오브젝트에 레플리카를 복수로 두고 잡 횟수를 늘려 테스트하면 레플리카 * 잡 회차가 burst에 미칠 때까지 순식간에 잡이 진행되고 이후 qps의 영향을 받는 것을 확인할 수 있다.
실행과 삭제를 같이 진행하면서 퍼센트 기반으로 잡 실행을 조절하고 싶다면 churn 기능을 쓰면 된다.
단순히 실행, 삭제를 한번에 하고 싶은 거라면 리스트로 생성 삭제 잡을 같이 넣어줘도 된다.
jobs:
- name: create-deployments
churn: true
churnDelay: 10s
jobIterations: 10
qps: 10
burst: 100
namespace: kube-burner-test
namespaceLabels: {kube-burner-job: delete-me}
waitWhenFinished: true # false
verifyObjects: false
preLoadImages: false
preLoadPeriod: 30s # default 1m
objects:
- objectTemplate: s1-deployment.yaml
파드 과다 테스트
이제부터 부하가 갈 만한 상황을 만들어본다.
jobIterations: 100
qps: 300
burst: 300
더 이상 배치되지 않고 내동댕이쳐진 파드들이 눈에 보인다.
describe를 하면 파드가 많아 노드에 스케줄링을 실패했다는 것을 확인할 수 있다.
노드의 정보를 보면 할당 가능한 파드의 개수가 최대 110개로 걸려있는 상황이다.
이걸 해결하려면 kubelet에 걸리는 인자를 수정해줘야 한다.
해결은 이렇게 할 수 있다.
docker exec -it myk8s-control-plane bash -c "echo maxPods: 400 >> /var/lib/kubelet/config.yaml"
docker exec -it myk8s-control-plane systemctl restart kubelet
docker exec -it myk8s-control-plane systemctl status kubelet
kubelet 프로세스 자체를 다시 띄워야 하는 일이라 api 서버를 거치는 것은 불가능하다.
사진은 400으로 최대 개수를 늘리기 이전 테스트를 위해 150으로 설정했다.
그럼 아예 더 못하게 파드 개수를 왕창 늘려보자.
kubelet 제한 뿐 아니라, IP 대역도 파드의 개수를 제한하는 하나의 요소가 된다.
jobIterations: 300
qps: 300
burst: 300
이번에는 스케줄은 전부 됐지만, 막상 노란색으로 점등되는 파드들이 산더미다.
이 파드들은 IP 주소를 할당 받지 못해 Container Creating 단계에서 에러가 발생하고 있다.
이 문제는 쉽게 해결하기 어렵다.
IP 주소를 관리하는 것은 CNI의 설정을 바꿔야 하는 사항이고, 현재 사용 중인 kindnet의 경우 동적으로 IP를 추가하는 기능은 지원하지 않는다.
api 집약적 작업 테스트
kube-burner에 있는 예시 테스트 중 하나를 수행해본다.
원래는 다 해볼 생각이었는데, 결국 테스트할 부하 발생 지점이 유달리 다르게 보이지는 않아서 다양한 기능을 찾아보는데 조금 더 주력한다.
일단 첫번째는 api에 단순하게 작업을 많이 요하는 일을 시키는 것이다.
이 잡 위에는 정말 별 거 없는 리소스들을 700개 가까이 만든다.
그리고는 patch 작업을 때려버리는 것이다.
아쉽게도 실험이 잘 진행되진 않았다. 도중에 또 IP 부족 문제가 터져 의미 없게 시간이 낭비됐다.
다만 위처럼 자원 사용량이 늘어나는 것까지는 확인이 됐다.
이후 실험에서는 qps, burst을 한껏 올려 진행했다.
이번엔 생각했던 상황이 연출되기는 했다.
사실 OOM 터지는 꼴 보고 싶었는데 실패
많은 요청이 들어왔고, 순간 요청이 500개 가까이 들어온 순간도 있었다.
모범 모니터링 쿼리 예제를 이용해서 확인해봤다.[5]
테스트가 되던 당시 요청 성공률이 절반으로 뚝 떨어지는 순간이 있었다.
들어오는 요청에 rate limiting이 이미 걸렸던 것으로 생각된다.
APF는 기본으로 설정돼있으니 이미 적용된 것일 수 있겠다.
문제 분석
빠르게 테스트를 한답시고 계속 지나쳤던 사항인데, 동작할 때 지속적으로 완전히 멈추는 구간이 발생했다.
지나치게 높게 버스트를 걸어서 아무래도 요청 중 드랍이 되는 것들이 있었던 게 아닐까 싶다.
드랍이 되고 요청이 수행되지 않아 다음으로 넘어가지 않고 파이프라인이 그대로 행된 것이 아닐까 추측된다.
메트릭을 봐도 지속적인 api 요청이 들어가기는 하는데, list 요청만 들어가고 있다.
그렇다면 api 요청 중에 429나, 관련한 에러 지표가 나왔을 거라 생각이 들어 찾아봤는데, 관련 메트릭 정보를 찾기는 어려웠다.
헬스체크가 실패하는 지표가 계속 관측되는데, 이것이 관련이 있을지는 모르겠다.
컨테이너 메트릭 정보도 찾아본다.
프로메테우스 컨테이너가 써먹는 자원이 많다는 것은 확인했다.
apf와 관련하여 문제가 생긴 것은 아닐 거라는 생각이 추가적으로 들게 됐는데, 만약 요청이 거부된다면 베타로 제공되는 rejected 관련 메트릭이 관측됐어야 할 것으로 보인다.[6]
그러나 막상 그러한 메트릭은 찾아볼 수 없었다.
그렇다면 마지막으로 의심 가는 것은 ip를 부여 받지 못해 펜딩된 파드를 처리하지 못해 지연이 발생하는 케이스이다.
펜딩 상태의 파드가 아직 수두룩하게 박혀 있고, 공교롭게도 현재 펜딩이 파드의 번호부터 파이프라인이 중단됐다.
이게 문제라면, 빠르게 부하를 내려했던 것 자체가 문제는 아니라는 이야기이기도 하다.
메트릭 확인
kube-burner의 기능 중 하는 메트릭과 연결해서 분석하는 것이다.
이를 위해 테스트가 될 당시의 모니터링 도구에 접근해 필요한 메트릭을 가져오고 이것을 어딘가에 넣을 수 있게 인덱싱한다.
인덱서로는 opensearch, elastic도 가능한데 로컬에 저장해야 당장 보기가 편하다.
아래와 같이 어디에서 메트릭을 긁어올지, 그럼 어떤 데이터를 긁을지, 이후 이걸 어디에 보낼 건지 확인하면 된다.
metricsEndpoints:
- endpoint: http://localhost:30001
metrics:
- etcdapi.yml
indexer:
metricsDirectory: /tmp/kube-burner
type: local
메트릭 파일은 이렇게 쿼리에 사용할 쿼리를 다룬 자료이다.
내 경우는 로컬로 설정하여 작업이 끝나면 이렇게 여러 json 파일이 생기는 것을 확인할 수 있다.
각가 json 형태로 메트릭 정보가 들어있는데 이걸 어떻게 시각화할진 조금 알아봐야겠다.
APF
API Priority and Fairness는 kube-apiserver로 들어가는 요청에 섬세한 제한을 거는 기능이다.
크게 보면 rate limiting이지만, APF는 최대 요청 제한에 대한 설정을 더 세밀하게 할 수 있도록 지원하며
또한 공정하게 처리될 트래픽을 분배하는 큐 알고리즘을 도입하여 특정 요청이 계속 처리되지 못하는 상황을 방지한다.
APF는 watch 요청에 대해서만 적용된다는 점 참고하자.
exec이나 log와 같은 커넥션이 긴 다른 요청에 대해선 간섭하지 않는다.
APF는 --enable-priority-and-fairness
인자로 전달하여 설정하며 기본 활성화 상태이다.
또한 관련 리소스가 존재하며 현재 v1 상태이다.
개념
동시성, 중요도(Priority Level)
단위 시간 동안 서버가 동시에 처리할 수 있는 양(concurrency capacity)은 정해져 있다.
이 수용량은 api 서버 인자 --max-requests-inflight
, --max-mutating-requests-inflight
를 통해 설정할 수 있다.
아무튼 문서에서는 동시성의 기본 단위를 seat이라 하는데, 이는 열차나 비행기에 전체 탑승 인원이 "좌석"을 기반으로 결정된다는 점에서 착안된 용어다.
예를 들면 100개의 자리가 있다면 한번에 서버는 100개의 요청을 처리할 수 있을 것이다.
물론 어떤 요청은 처리에 시간이 오래 걸리거나 많은 연산을 수행해야 하여 더 많은 자리를 차지할 수도 있다.
그래서 자리는 그저 동시성 수용량을 부분으로 쪼개 이해하기 위한 개념 정도로 생각하면 될 듯하다.
APF는 이 동시성 수용량을 쪼개 그룹 별로 할당하는 역할을 수행한다.
API 서버로 들어오는 요청의 속성 기반으로 분류한 뒤, 분류된 그룹에 따라 정책을 적용하는 방식으로 분류된 그룹을 Priority Level(중요도라 번역하겠다)이라 부른다.
중요도는 요청을 동시에 처리할 수 있는 공간을 나눠가지는 단위이기에, 다른 중요도에 속한 요청들은 서로의 자원을 뺏는 일이 발생하지 않는다.
그래서 파드에서 오는 요청이 많더라도 파드 스케줄링을 위한 kubelet의 요청에는 영향이 가지 않도록 세팅하는 것이 가능하다.
중요도 별 동시성 제한은 주기적으로 조정된다.
가령 활용도가 낮은 중요도 그룹의 동시성 수용량을 줄이고 부하가 큰 중요도 그룹의 수용량을 넓혀준다!
조정이 일어나는 범위에 대해서는 제한을 두거나 설정하는게 가능한데, 아래 리소스에서 자세히 보도록 한다.
큐
중요도가 다른 그룹 간에는 서로 경합이 일어나지 않지만, 중요도 내의 여러 요청은 자원을 나눠써야 하는 입장이다.
또한 한 중요도에 많은 요청이 몰리면 당연히 버려지는 요청도 생기게 될 것이다.
APF는 이에 대해 큐를 사용한다.
특히 공정한 큐 알고리즘을 사용해 중요도 내 여러 요청들이 공평하게 수행될 수 있도록 돕는다.
구체적으로는 셔플 샤딩(큐에 해싱 나머지 연산을 이용해 배치)을 사용하는데, 알고리즘은 중요도 별로 유형을 달리 할 수 있다.
이러한 튜닝을 통해 큐에서 메모리를 더 사용하도록 공간을 넓힐 수도 있고, 아예 큐를 두지 않는 것도 가능하다.
각 요청은 요청자, 대상 네임스페이스 등을 구분자로 매칭되는 flow schema로 식별되는 플로우을 하나씩 지정받는다.
같은 중요 레벨의 플로우들은 거의 같은 가중치를 부여한다.
한 플로우에서 요청을 분류하면 그 다음에 큐에 배치한다.
관련 리소스
위의 개념을 바탕으로 APF에서는 두 가지 리소스를 제공한다.
PriorityLevelConfiguration
이건 중요도 그룹을 지정하는 리소스로, 그룹 별 동시성 수용량, 큐 튜닝 등의 설정을 할 수 있다.
중요도 수치를 설정할 수도 있는데, 이건 전체 동시성 양에서 비율적으로 산정된다.
그래서 이를 명목 상(nominal)의 숫자라고도 표현한다.
- type - 동시 처리량을 초과하는 요청을 어떻게 할지 지정
- Reject - 429 에러(Too Many Requests)
- Queue - 임계치 이상이면 큐에 적재
다음은 큐 관련 설정이다.
- queues - 큐의 개수를 설정하는 것으로 이것을 늘리면 다른 플로우간 충돌률을 줄인다.
- 메모리 사용량이 늘어난다.
- 1은 큐잉 로직을 비활성화한다.
- queueLengthLimit - 큐 사이즈 자체를 늘린다.
- handSize - 다른 플로우들과 전체 동시성 상에서 발생할 충돌 가능성을 조정한다.
- 핸드사이즈가 크면 두 개별 플로우 간 충돌을 줄인다.
- 대신 api 서버가 한번에 감당 가능한 플로우 개수를 줄인다.
- 단일 플로우에서 큐에 들어갈 수 있는 최대 요청 개수는 handSize * queueLengthLimit
FlowSchema
요청의 속성을 조건으로 중요도 그룹에 매핑하는 설정 리소스이다.
apiVersion: flowcontrol.apiserver.k8s.io/v1
kind: FlowSchema
metadata:
name: health-for-strangers
spec:
matchingPrecedence: 1000
priorityLevelConfiguration:
name: exempt
rules:
- nonResourceRules:
- nonResourceURLs:
- "/healthz"
- "/livez"
- "/readyz"
verbs:
- "*"
subjects:
- kind: Group
group:
name: "system:unauthenticated"
- matchingPrecedence - 정책 적용 순위. 낮은 녀석부터 요청에 조건 검증이 일어나며, 첫번째로 부합한 조건에 맞춰 중요도가 결정된다.
- rules - 실제 조건을 작성하는 필드로 여기에 한 조건이라도 매칭되면 적용된다.
distinguisherMethod.type은 요청이 플로우에 들어갈 때의 타입.
ByUser - 다른 유저의 수용량을 뺏지 않음
ByNamespace - 네임스페이스별 수용량을 뺏지 않음
설정 안하면 그냥 설정 안된다.
그럼 모든 요청이 하나의 단일 플로우로 인식될 것이다.
기본 APF
api 서버에는 mandatory, suggested 두 유형으로 먼저 APF 기능이 들어가 있다.
mandatory
여기에는 2개의 중요도가 들어있다.
- exempt
- 플로우 제어에 들어가지 않고 바로 처리되는 중요도.
system:masters
그룹의 요청은 이 중요도를 받는다.
- catch-all
- catch-all 플로우 스키마에 해당하는 중요도.
- 이걸 직접 쓰기보다는 커스텀해서 쓰자.
- 이건 굉장히 공유도 적고 큐잉을 하지 않는다.
suggested
- node-high
- 노드 헬스체크용
- system
system:nodes
그룹인데 헬스체크는 아닌 요청. 가령 kubelet에서 스케줄링된 파드 조회
- leader-election
- 내장 컨트롤러들의 요청. 가령 endpoint, configmap, lease 관리
- workload-high
- 내장 컨트롤러의 다른 요청들
- workload-low
- 서비스 어카운트로부터 오는 요청
- global-default
- 나머지 모든 요청
APF 확인
많이 사용해본 적도 없고, 구체적으로 어떻게 설정하는지 명확하게는 머리에 잘 안 들어와서 이번 실습에서는 확인하는 시간 정도만 가진다.
플로우 스키마, 중요도 설정 모두 기본으로 설정된 리소스가 확인된다.
스키마는 매칭 우선순위에 따라 정렬돼 있는 것을 확인할 수 있다.
보다시피 system 그룹이면 exempt, 즉 예외 중요도 그룹으로 매핑된다.
그러면 해당 요청들은 apf에 당하지 않고 그대로 요청이 수행된다.
apf는 현재 관련한 메트릭도 프로메테우스 양식으로 공개하고 있다.
결론
실리움 스터디에 실리움보다 쿠버네티스 자체 최적화에 내용을 더 디테일하게 다뤘다..
그러나 결국 운영 상에서 어떤 부분에서 병목이 발생하는지는 비슷하기 때문에, 실리움을 본격적으로 파기 전에 클러스터 자체에 대한 성능을 최적화하는 기법을 알아두는 것은 충분한 도움이 될 것이다.
이전 글, 다음 글
- 6W - 실리움 서비스 메시 - 인그레스: 12
- 8W - 실리움 보안: 14
다른 글 보기
이름 | index | noteType | created |
---|---|---|---|
1W - 실리움 기본 소개 | 1 | published | 2025-07-19 |
1W - 클러스터 세팅 및 cni 마이그레이션 | 2 | published | 2025-07-19 |
1W - 기본 실리움 탐색 및 통신 확인 | 3 | published | 2025-07-19 |
2W - 허블 기반 모니터링 | 4 | published | 2025-07-26 |
2W - 프로메테우스와 그라파나를 활용한 모니터링 | 5 | published | 2025-07-26 |
3W - 실리움 기본 - IPAM | 6 | published | 2025-08-02 |
3W - 실리움 기본 - Routing, Masq, IP Frag | 7 | published | 2025-08-02 |
4W - 실리움 라우팅 모드 실습 - native, vxlan, geneve | 8 | published | 2025-08-09 |
4W - 실리움 로드밸런서 기능 - 서비스 IP, L2 | 9 | published | 2025-08-09 |
5W - BGP 실습 | 10 | published | 2025-08-16 |
5W - 클러스터 메시 | 11 | published | 2025-08-16 |
6W - 실리움 서비스 메시 - 인그레스 | 12 | published | 2025-08-23 |
7W - 실리움 성능 - 쿠버네티스 기본 | 13 | published | 2025-08-31 |
8W - 실리움 보안 | 14 | published | 2025-09-07 |
관련 문서
지식 문서, EXPLAIN
이름3 | is-folder | 생성 일자 |
---|---|---|
E-이스티오 컨트롤 플레인 성능 최적화 | false | 2025-05-18 02:29 |
클러스터 성능 최적화 | false | 2025-08-30 17:02 |
API Priority and Fairness | false | 2025-08-30 17:08 |
기타 문서
Z0-연관 knowledge, Z1-트러블슈팅 Z2-디자인,설계, Z3-임시, Z5-프로젝트,아카이브, Z8,9-미분류,미완이름1 | 코드 | 타입 | 생성 일자 |
---|---|---|---|
6W - 이스티오 컨트롤 플레인 성능 최적화 | Z8 | published | 2025-05-18 02:29 |
참고
https://kube-burner.github.io/kube-burner/latest/measurements/#metrics_1 ↩︎
https://github.com/kube-burner/kube-burner/blob/main/examples/workloads/network-policy/network-policy.yml ↩︎
https://www.redhat.com/en/blog/kubernetes-api-performance-metrics-examples-and-best-practices ↩︎
https://kubernetes.io/docs/concepts/cluster-administration/flow-control/#maturity-level-beta ↩︎